技术分享 | 零拷贝技术在网络报文处理中的应用
熟悉Linux内核或者从事于网络开发的人经常会听到一个词,叫“零拷贝技术”。从字面意思很容易理解这代表什么,但很少有人真正去了解使用这个技术要解决什么问题和这个技术怎么解决问题。本文从问题开始,到技术原理介绍,最后回归到问题本质来介绍“零拷贝技术”在网络报文处理中的应用。
与互联网情况下的报文处理流程相比,许多工业现场的关键业务具有很强的时间敏感性,即对时延要求非常高。那么对网络报文如何处理,才能在实时性要求较高的客户系统不影响现场业务的前提下,达到安全防护的目的?
一般设备在处理报文时有两种方式:一种是基于内核,把业务通过挂载点嵌入到内核协议栈的某个处理流程中进行相应的处理;另外一种是把报文绕过Linux系统内核协议栈,将报文从内核向用户空间直接传递。基于内核的大家比较熟悉的就是Iptables防火墙,而绕过内核协议栈处理,直接使用链路层PF_PACKET协议族原始套接字方式向用户空间传递报文的是libpcap,应用这种方式的开源框架比较有名的是Suricata。那么,这两种方式都对报文做了哪些主要的处理呢?
2.1 IPtables防火墙
Iptables其实不是真正的防火墙,说它是一个客户端代理更加贴切,用户通过iptables这个代理,将用户的安全策略配置到对应的“安全框架”中,这个“安全框架”才能起到防火墙应起的作用,它就是所谓的netfilter框架,位于内核空间,熟悉linux网络协议栈的都对它耳熟能详。Iptables本质上只不过是一个命令行工具,它位于用户空间,用户通过这个工具来配置相应的安全规则,从而iptables + netfilter才组成了linux的包过滤防火墙。下图是iptables防火墙典型的报文处理流程:
图 1 报文处理流程示意图
我们都知道,linux内核协议栈非常庞大和复杂,在多数应用场景下报文的处理过程并不需要这么复杂的处理过程,并且有些业务在内核来做是非常不适合的,一不小心业务代码就会造成系统原本的稳定性变差,进而严重时会造成系统崩溃。基于内核协议栈的报文处理灵活性差、性能低、可扩展性不强,现在越来越多的网络业务都抛开了linux系统的内核协议栈,转而直接在应用层处理,提高了系统的稳定性,也丰富了业务应用。所以本文就不再讨论基于内核的这种场景了。
2.2 libpcap收发包
Libpcap是通过这样的机制进行工作的:一个网络报文到达网卡时,通过网络分支接口(旁路机制)将网络报文发给BPF,匹配通过的网络报文可以从链路层驱动程序中直接取得,进而在用户空间提供用户级API接口,这些都独立于系统内核协议栈。下图为libpcap收发包示意图:
图 2 libpcap收发包示意图
下面,我们详细解释libpcap如何将报文从网卡获取到最终传送到用户空间的处理流程:
• 网络报文的接收源自网络设备(网卡)。
---------------------------物理层
• 处理网络数据的设备首先接收到一个报文之后,通过中断IRQ告知CPU。网卡Driver已注册过该中断事件对应的处理函数,所以就可以处理此报文。在中断中执行以下操作:
• 分配一个缓冲区sk_buff,把接收的数据拷贝进去;(首次拷贝)
• 对缓冲区结构内的一些参数做初始化以告知较高层protocol数据是什么类型;
• 非NAPI:调用接口netif_rx函数通知内核,将帧放入CPU的softnet_data->input_pkt_queue。netif_rx会调用网络接口函数netif_rx_schedule(使用softdate_net结构中内嵌的backlog_dev作为dev参数)
• NAPI:帧存放在每个设备自己的队列之中。调用netif_rx_schedule函数(直接以对应设备的dev结构为参数)
• 然后触发相关联的软IRQ--NET_RX_SOFTIRQ,此时网卡驱动程序已经将输入设备排入轮询列表poll_list,接下来执行net_rx_action函数:
• 浏览poll_list设备列表,这些设备的入口队列都有数据;
• 非NAPI设备:执行process_backlog函数(backlog_dev->poll)
若时间片用完或者配额用尽,将该设备放置列表尾部等待下一次中断到来时继续被调用;若处理完input_pkt_queue列表中的全部报文,则将设备退出poll_list同时打开设备中断服务继续监听下一个报文到来。
• NAPI设备:执行poll函数
dev->poll可以做一些轮询的工作,如果网络设备已经接收了多个报文,可以一次性处理。就算设备此刻所接收到的报文都已经处理完了,驱动程序也可以根据某种方式预判设备在很短的一段时间内还将收到报文,于是依然将自己对应的dev结构留在poll_list中,处于轮询状态。增大了报文接收的平均延时,但避免了大量中断带来的开销。dev设备退出poll_list同时打开设备中断服务。
• 接着调用netif_receive_skb函数:
• 如果有抓包程序,由网络分接口进入BPF过滤器,将规则匹配的报文拷贝到系统内核缓存 (再次拷贝)否则直接丢弃数据包;*注 : linux 在 PF_PACKET 类型的 socket 上支持内核过滤。Linux 内核允许我们把一个名为 LPF(Linux Packet Filter) 的过滤器直接放到 PF_PACKET 类型 socket 的处理过程中,过滤器在网卡接收中断执行后立即执行 *
• 处理数据链路层的桥接功能;
• 根据skb->protocol字段确定上层协议并提交给网络层处理-->进入网络协议栈
|
|
| 内核空间
|
/
---------------------------数据链路层
/
|
| 用户空间
|
|
• libpcap绕过了Linux内核收包流程中协议栈部分的处理,使得用户空间API可以直接调用套接字PF_PACKET从链路层驱动程序中获得数据报文的拷贝,将其从内核缓冲区拷贝至用户空间缓冲区(第三次拷贝)
fd=socket(PF_PACKET,sock_RAW,htons(ETH_P_ALL))
libpcap 函数库注册的报文接收类型为 ETH_P_ALL,即接收所有的网络数据帧,其处理函数为 packet_rcv()。该函数工作在数据链路层。
进而调用recvfrom函数获得捕获的报文 (需要进行系统调用):
packet_rcv() 函数将直接调用 skb_queue_tail() 将数据报文存放在代表相应网络连接控制结构(struct sock)的接收队列 receive_queue 中。这样数据报文在接收过程中就绕过了TCP 层和IP层繁琐的协议处理过程。最后,睡眠在sk 等待队列上的函数packet_recvmsg()会接收链路层数据帧并将该数据帧直接拷贝到应用程序缓冲区中。
• 最后libpcap面向用户空间提供独立于系统的可调用的函数接口
从上述的流程中可以看出,共产生了三次数据拷贝,与此同时,在用户态与内核态也发生了多次上下文切换,无疑加重了CPU负担。更严重的是,在此过程中,我们没有对报文内容做任何修改,那么在内核空间和用户空间来回拷贝数据无疑就是一种浪费,这样做会极大影响处理效率,难以满足敏感时延位置的要求,而零拷贝主要就是为了解决这种低效性。
简单一点来说,一种用来避免浪费CPU资源将数据从一块存储(如:内核区网络报文数据)拷贝到另外一块存储(如:用户态网络报文数据)的技术就是零拷贝。为了极大地提升业务应用处理数据的性能,针对操作系统中的devices驱动程序、file system以及应用层处理网络协议数据而出现了多种的零拷贝技术,正是使用了零拷贝技术避免了宝贵CPU资源的浪费,使得这些业务应用程序利用系统资源更加的有效。零拷贝的这种技术好处在于,可以有效减少数据拷贝及共享总线操作的次数。当在不同的存储器之间传输数据时,消除一些不必要的拷贝过程,将CPU专注于做该做的事情,从而将数据传输效率有效地提高。另外,这种零拷贝技术还减少了业务应用程序地址空间与Linux系统内核地址空间之间进行上下文切换而带来的资源消耗。在不同的存储器之间进行大量的数据拷贝任务操作其实是简单的,从OS的角度讲,如果宝贵的CPU资源一直被用来去执行这种非常简单的任务,并且这种拷贝并不改变数据本身,仅是在不同的存储器之间移动,那么这是对资源的极大浪费;如果能将这种资源浪费的操作省去,从而将CPU真正的解脱出来去做更重要的事情,那么系统资源的利用则会更加有效。综上所述,可以将零拷贝技术的目标概括为如下几条:
◇ 减少拷贝次数
避免Linux系统的内核缓冲区之间、内核和用户业务应用程序之间进行数据拷贝操作,并且尽量使业务应用程序可以避开OS直接访问硬件的存储,如果实在需要数据传输,就尽量让 DMA 来做。
◇ 合并多种操作
尽可能的避免不必要的上下文切换及系统调用,数据需要拷贝时可先将其缓存,尽可能的交给硬件对数据进行处理。
威努特工业防火墙在业内率先使用“零拷贝”技术,其主要报文处理流程如下图:
图 3 “零拷贝”技术报文主要处理流程
威努特工业防火墙除了使用这种先进的网络报文零拷贝技术,同时配合使用专用网络处理芯片,使得报文经过防火墙的时间非常短,平均时间只有几到几十微秒,可满足实时工业现场的防护要求。下图为相关检测机构检测的实际时延指标:
更多阅读:
改革开放40年·两化融合丨尹丽波:工业信息安全,全力护航强国建设
威努特助力西部关键基础设施建设,新疆石油石化行业工控安全技术研讨会成功召开
威努特工控安全
专注工控·捍卫安全